import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';

import 'package:file_transfer/model/message_model.dart';
import 'package:file_transfer/model/simple_model.dart';
import 'package:file_transfer/pojo/channel_message.dart';
import 'package:file_transfer/pojo/message.dart';
import 'package:file_transfer/store/file_store.dart';
import 'package:file_transfer/store/user_store.dart';
import 'package:file_transfer/utils/toast_util.dart';
import 'package:flutter/material.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:path_provider/path_provider.dart';
import 'package:provider/provider.dart';
import 'package:synchronized/synchronized.dart';
import 'package:web_socket_channel/io.dart';
import 'package:file_transfer/route/simple_route.dart';
import 'package:path/path.dart' as path;


class _FileInfo{
  String name;
  int size;
  String client;
  _FileInfo({this.name = "", this.size =0, this.client = ""});

  factory _FileInfo.fromJson(String json){
    final data = jsonDecode(json);
    return _FileInfo(name:data["name"], size:data["size"], client:data["client"]);
  }

  @override
  String toString() {
    return '_FileInfo{name: $name, size: $size, client: $client}';
  }
}

class _DataChannel{
  // 本地channel，主要用来接收消息
  RTCDataChannel? chatChannel;
  RTCDataChannel? fileChannel;
  // 远程channel，主要用来发消息
  RTCDataChannel? chatDataChannel;
  RTCDataChannel? fileDataChannel;
}
class WebrtcManager {
  MessageModel? messageModel; // 消息model
  SimpleModel? simpleModel; // 通用model

  // SimpleRoute? simpleRoute; // 全局路由
  WebrtcManager._internal();

  // 连接超时时间,单位毫秒
  static const int connectionTimeoutPeriod = 3000;

  static final WebrtcManager _instance = WebrtcManager._internal();
  factory WebrtcManager.instance(BuildContext context) {
    _instance.messageModel ??= context.read<MessageModel>();
    _instance.simpleModel ??= context.read<SimpleModel>();
    // _instance.simpleRoute ??= SimpleRoute.instance(context);
    return _instance;
  }

  // pc列表
  final Map<String,RTCPeerConnection> _pcMap = {};
  final Map<String,_DataChannel> _dataChannelMap = {};

  final RTCDataChannelInit _dataChannelDict = RTCDataChannelInit();
  final Map<String,dynamic> _pcConfiguration = {
    "iceServers": [
      {"urls": "stun:stun.l.google.com:19302"}
    ]
  };
  final Map<String,dynamic> _sdpConfiguration = {};
  final Map<String, dynamic> _pcConstraints = {};

  IOWebSocketChannel? _ws;

  // 登录返回的信息
  User? user;

  // 接收的文件信息
  _FileInfo _fileInfo = _FileInfo();

  // 周期发送心跳
  Timer? _heartbeatTimer;
  // 最大重连次数
  static const int maxRetryCount = 5;
  // 重连次数
  int _retryCount = 0;

  // ws服务器初始化
  String? url;
  // ws连接锁
  final Lock _wsLock = Lock();
  // 异步连接锁
  final Lock _asyncConnectLock = Lock();
  // login锁
  final Lock _loginLock = Lock();
  connectServer(String url){
    if(simpleModel!.wsConnectState == true){
      return;
    }
    _wsLock.synchronized(() async{
      if(simpleModel!.wsConnectState == true){
        return;
      }

      this.url = url;
      // ws://192.168.1.10:8900
      // 加载文件配置信息
      _loadFileConfig();
      _ws = IOWebSocketChannel.connect(url);
      _ws!.stream.listen((event)=>_handlerMessage(event),onError: (e){
        debugPrint("连接ws失败：$e");
        ToastUtil.show("服务器连接失败！",backgroundColor: Colors.red);
      },onDone: _handlerOnDone);


      await _ws!.ready;
      // 连接成功触发
      connInit(){
        if(simpleModel!.wsConnectState == true){
          return;
        }
        if(_retryCount > 2){
          ToastUtil.show("连接已恢复",textColor: Colors.white,backgroundColor: Colors.green);
        }
        // 重置重连次数
        _retryCount = 0;
        // 成功连接ws，设置状态
        simpleModel!.wsConnectState = true;
        func(){
          // 连接成功之后，尝试登录
          _loadLogin();
          // 设置心跳
          _handleHeartbeat();
        }
        _asyncConnectLock.synchronized(func);
      }
      // _ws!.ready.then();
      connInit();
    });
  }

  _handleHeartbeat() async{
    _heartbeatTimer = Timer.periodic(const Duration(seconds: 30), (timer) {
      if(_ws != null && _ws!.closeCode == null) {
        _sendSingleMessage(HeartbeatMessage(HeartbeatType.ping));
      }else{
        // 如果ws已经关闭则，取消定时器
        timer.cancel();
      }
    });
  }

  _loadFileConfig() async {
    String? path = await getFileConfig();
    if(path == null){
      final sd = await getExternalStorageDirectory();
      path = sd!.path;
    }
    simpleModel!.receiveDirectoryPath = path;
  }

  _loadLogin() async{
    if(simpleModel!.wsConnectState && simpleModel!.isLogin){
      return;
    }

    final user =await getUserData();
    if(user == null){
      return;
    }

    String? name;
    try {
       name = jsonDecode(user)["name"];
    } catch (e) {
      debugPrint("登录失败，$e");
    }

    if(name== null) {
      debugPrint("获取本地用户信息失败");
      return;
    }

    final login = {
      "msgtype":TypeMessage.login,
      "name":name
    };

    _loginLock.synchronized((){
      // 双重判断
      if(simpleModel!.wsConnectState && simpleModel!.isLogin){
        return;
      }
      final data = json.encode(login);
      _ws!.sink.add(data);
      // 设置为已登录
      simpleModel!.isLogin = true;
    });

  }

  _handlerOnDone(){
    // 连接关闭立马关闭定时器
    _heartbeatTimer?.cancel();

    simpleModel!.wsConnectState = false;
    simpleModel!.isLogin = false;

    debugPrint("retry Count $_retryCount : $maxRetryCount");
    if(_retryCount >= maxRetryCount){
      // 连接失败，不再重试连接  连接已关闭
      return;
    }

    // 尝试重新连接
    _retryCount += 1;
    // 等待3s后重连
    Future.delayed(const Duration(seconds: 5),()=>connectServer(url!));
    // connectServer(url!);
  }

  // 登录
  bool login(String name){
    // 必须在连接状态下才能登录
    final wsState = simpleModel!.wsConnectState;
    if(!wsState){
      debugPrint("与服务器连接断开");
      return false;
    }

    final login = {
      "msgtype":TypeMessage.login,
      "name":name
    };
    final data = json.encode(login);
    _ws!.sink.add(data);

    // 登录成功，信息持久化
    final userData = {
      "name":name
    };
    saveUserData(jsonEncode(userData));
    return true;
  }
  
  _handlerMessage(String message) async {
    // todo 要改协议的部分
    final data = json.decode(message);
    final int msgtype = data["msgtype"];
    debugPrint("收到消息：$message");
    switch (msgtype) {
      case TypeMessage.login:
        _handleLogin(data);
        break;
      case TypeMessage.signal:
        await _handeSingleMessage(message);
        break;
      case TypeMessage.text:
        ToastUtil.show("服务端消息：${data['data']}",textColor: Colors.yellowAccent);
        debugPrint("收到文本消息：${data['data']}");
        break;
      case TypeMessage.user:
        _handleUserMessage(data);
        break;
      default:
        debugPrint('未知消息类型');
        break;
    }
  }

  _handleLogin(Map<String,dynamic> message){
    // 数据持久化
    user = User(name: message["name"], key: message["key"]);
    // 将登录信息添加到model
    simpleModel!.userInfo = User(name: user!.name,key: user!.key);
    // 设置为已登录
    simpleModel!.isLogin = true;
    // ToastUtil.show("登录成功",backgroundColor: Colors.green);
  }

  _handleUserMessage(Map<String,dynamic> message){
    debugPrint("handleUserMessage:$message");
    final list = message["data"];
    final userMessage = UserMessage.fromJson(list);
    userMessage.data.removeWhere((user) => user.key == this.user!.key);

    // todo 更新在线客户端
    messageModel!.onlineUserList = userMessage.data;

    final keyList = userMessage.data.map((e) => e.key).toList();
    // 移除已经离线的设备连接
    _pcMap.removeWhere((key, _){
      return !keyList.contains(key);
    });
    _dataChannelMap.removeWhere((key, _) => !keyList.contains(key));
  }

  _handeSingleMessage(String message) async{
    // todo 出错的逻辑要改就是变成多个用户
    final singleMessage = SignalMessage.fromJson(json.decode(message));
    final type = singleMessage.type;

    if(type == "offer"){
      await _handleOffer(singleMessage);
    }else if(type == "answer"){
      await _handleAnswer(singleMessage);
    }else if(type == "candidate"){
      await _handleCandidate(singleMessage);
    }
  }

  _sendSingleMessage(Message message){
    final data = jsonEncode(message.toJson());
    debugPrint("_sendSingleMessage:$data");
    _ws!.sink.add(data);
  }
  
  sendOffer() async{

    final key =  messageModel!.client.key;
    if(_dataChannelMap.containsKey(key)){
      final dataChannel = _dataChannelMap[key];
      if(dataChannel!.chatDataChannel != null && dataChannel.chatDataChannel!.state == RTCDataChannelState.RTCDataChannelOpen){
        debugPrint("已经建立了连接");
        return;
      }
    }


    RTCPeerConnection? pc;
    if(!_pcMap.containsKey(key)){
      await _getPc();
    }
    pc = _pcMap[key];

    final offer = await pc!.createOffer(_sdpConfiguration);
    pc.setLocalDescription(offer);
    final singleMessage = SignalMessage(client: key, type: "offer", data: offer.toMap());
    _sendSingleMessage(singleMessage);

    // todo 发送offer并不能马上建立连接，所以需要阻塞轮询，知道确认建立了连接再返回
    int tmpTime = 0;
    while (true) {
      // 时间大于超时时间就为连接失败
      if(tmpTime >= connectionTimeoutPeriod){
        _handlerFailConnection();
        throw FlutterError("与${messageModel!.client.key}连接建立失败");
      }
      tmpTime += 100;

      if (_dataChannelMap.containsKey(key)) {
        final dataChannel = _dataChannelMap[key];
        if (dataChannel!.chatDataChannel != null &&
            dataChannel.chatDataChannel!.state ==
                RTCDataChannelState.RTCDataChannelOpen) {
          return;
        }
      }
      await Future.delayed(const Duration(milliseconds: 100));
    }
  }

  _handlerFailConnection(){
    final t = messageModel!.onlineUserList.firstWhere((e)=>e.key == messageModel!.client.key,orElse: () => User(key: ""));
    messageModel!.addChatMessage(SystemMessage(t.name));
    // ToastUtil.show("连接失败了${messageModel!.client.key}");
  }


  _handleOffer(SignalMessage signalMessage) async {
    messageModel!.client.key = signalMessage.client;

    final key = signalMessage.client;
    RTCPeerConnection? pc;
    if(!_pcMap.containsKey(key)){
      await _getPc();
    }
    pc = _pcMap[key];

    final data = signalMessage.data;
    // todo 这里应该能优化
    await pc!.setRemoteDescription(RTCSessionDescription(data["sdp"], data["type"]));
    final answer = await pc.createAnswer(_sdpConfiguration);
    await pc.setLocalDescription(answer);
    final singleMessage = SignalMessage(client: key, type: "answer", data: answer.toMap());
    _sendSingleMessage(singleMessage);
  }
  
  _handleAnswer(SignalMessage signalMessage) async{
    final key = messageModel!.client.key;
    RTCPeerConnection? pc;
    if(!_pcMap.containsKey(key)){
      await _getPc();
    }
    pc = _pcMap[key];


    final data = signalMessage.data;
    await pc!.setRemoteDescription(RTCSessionDescription(data["sdp"], data["type"]));
  }

  final _iceLock = Lock();
  _handleCandidate(SignalMessage signalMessage) async{
    await _iceLock.synchronized(() async{
      messageModel!.client.key = signalMessage.client;

      final key = signalMessage.client;
      RTCPeerConnection? pc;
      if(!_pcMap.containsKey(key)){
        await _getPc();
      }
      pc = _pcMap[key];


      final data = signalMessage.data;
      final String candidate = data["candidate"];
      final String sdpMid = data["sdpMid"];
      final int sdpMLineIndex = data["sdpMLineIndex"];
      await pc!.addCandidate(RTCIceCandidate(candidate, sdpMid, sdpMLineIndex));
    });

  }

  final lock = Lock();
  _getPc() async{
    await lock.synchronized(() async{
      final key = messageModel!.client.key;
      if (!_pcMap.containsKey(key)) {
        try {
          RTCPeerConnection pc;
          // 初始化本地连接
          pc = await createPeerConnection(_pcConfiguration, _pcConstraints);
          pc.onIceCandidate = _onIceCandidate;

          // 设置channel监听回调
          // 如果map不存在，就创建出来，只有第一次需要创建
          if (!_dataChannelMap.containsKey(key)) {
            _dataChannelMap[key] = _DataChannel();
          }
          // dataChannel缺什么添加什么
          _dataChannelDict.maxRetransmits = 30;//最大重传次数
          _dataChannelMap[key]!.chatDataChannel ??= await pc.createDataChannel("chat", _dataChannelDict);
          _dataChannelMap[key]!.fileDataChannel ??= await pc.createDataChannel("file", _dataChannelDict);


          pc.onDataChannel = _onDataChannel;
          // 添加pc
          _pcMap[key] = pc;
        } catch (e) {
          debugPrint("getPc异常：$e");
        }
      }
    });

    // return _pcMap[key];
  }

  _onIceCandidate(RTCIceCandidate iceCandidate) async{
    debugPrint("生成的ice信息：${iceCandidate.toMap()}");
    final singleMessage = SignalMessage(client: messageModel!.client.key, type: "candidate", data: iceCandidate.toMap());
    _sendSingleMessage(singleMessage);
  }

  _onDataChannel(RTCDataChannel channel){
    final key = messageModel!.client.key;
    // 到这一步，肯定是有dataChannel对象的，可以直接拿到
    final dataChannel = _dataChannelMap[key];
    if(channel.label == "chat") {
      RTCDataChannel chatChannel = channel;
      if(dataChannel!.chatChannel == null) {
        dataChannel.chatChannel = chatChannel;
      }
      chatChannel.onMessage = _onChatChannelMessage;
    }else if(channel.label == "file"){
      RTCDataChannel fileChannel = channel;
      if(dataChannel!.fileChannel == null){
        dataChannel.fileChannel = fileChannel;
      }
      fileChannel.onMessage = _onFileChannelMessage;
    }
  }

  _onChatChannelMessage(RTCDataChannelMessage message){
    debugPrint("onChannelMessage:${message.text}");
    // todo 接收的消息
    messageModel!.receiveMessage = message.text;
    // 添加接收的文件信息
    messageModel!.addChatMessage(TextChannelMessage.fromJsonRemote(
    jsonDecode(message.text)));

  }

  // 接受的文件片段列表
  final List<Uint8List> _receivedFileChunks = [];
  _onFileChannelMessage(RTCDataChannelMessage message){
    if(!message.isBinary) {
      final data = json.decode(message.text);
      final String type = data["type"];
      if(type == "fileInfo"){
        _fileInfo = _FileInfo.fromJson(message.text);
        _receivedFileChunks.clear();
        // 添加接收的文件信息
        final fileMessage = FileChannelMessage.fromJsonRemote(data);
        messageModel!.addChatMessage(fileMessage);
        // 设置当前接收的文件
        messageModel!.receivingFile = fileMessage;
      }else if(type == "end"){
        debugPrint("文件接收成功");
        ToastUtil.show("文件(${messageModel?.receivingFile.name})接收成功",backgroundColor: Colors.green);

        // 深拷贝接收的文件块
        final receivedFileChunks = _receivedFileChunks.map((e) => e).toList();
        _handeReceivedFile(receivedFileChunks);
      }
      debugPrint("onChannelMessage:${message.text}");
    }else{
      // 处理二进制流文件
      _receivedFileChunks.add(message.binary);
      // 添加接收文件进度,使用函数式变成获取总长度
      messageModel!.receiveFileProgress = _receivedFileChunks.map((e)=>e.length).toList()
      .reduce((value, element) => value + element);
    }
  }

  _handeReceivedFile(List<Uint8List> receivedFileChunks) async{
    String path = simpleModel!.receiveDirectoryPath;
    if(path == ""){
      final sd = await getExternalStorageDirectory();
      path = sd!.path;
    }
    // 将接收的文件存储到指定路径

    File file = File("$path/${_fileInfo.name}");
    // 重名文件重新命令
    File rename() {
      final x = _fileInfo.name.split(".");
      final name = x.length > 1 ? x.sublist(0, x.length - 1).join('.') : x.first;
      final ext = x.length > 1 ? x.last : '';
      int n = 1;
      String newPath = ext.isNotEmpty ? "$path/$name($n).$ext" : "$path/$name($n)";
      File newFile = File(newPath);
      while (newFile.existsSync()) {
        n++;
        newPath = ext.isNotEmpty ? "$path/$name($n).$ext" : "$path/$name($n)";
        newFile = File(newPath);
      }
      return newFile;
    }
    file = file.existsSync()? rename() : file;

    final os = file.openWrite();
    try{
      receivedFileChunks.forEach(os.add);
    }finally{
      await os.close();
    }

    // final bytes = Uint8List.fromList(receivedFileChunks.expand((x) => x).toList());
    // 大文件内存不足，使用stream流处理
    // await file.writeAsBytes(bytes);
  }


  /* 发送文件 */
  static const int _chunkSize = 64 * 1024; // 每个分片64KB
  sendFile(File file) async {
    final key =  messageModel!.client.key;
    await sendOffer();
    final dataChannel = _dataChannelMap[key];
    final fileDataChannel = dataChannel!.fileDataChannel!;

    final List<int> bytes = await file.readAsBytes();
    final int totalChunks = (bytes.length / _chunkSize).ceil();

    final name = path.basename(file.path);
    final len = await file.length();
    // 先发送文件信息
    final fileInfo = {
      "type":"fileInfo",
      "name":name,
      "size": len,
      "client":simpleModel?.userInfo.name,
    };
    fileDataChannel.send(RTCDataChannelMessage(jsonEncode(fileInfo)));
    // 添加本地消息
    final fileMessage = FileChannelMessage.fromJsonLocal(fileInfo);
    messageModel!.addChatMessage(fileMessage);
    // 添加本地文件发送信息
    messageModel!.sendingFile = fileMessage;
    // 发送进度前，先清空上一次的进度
    messageModel!.sendFileProgress = 0;
    // 文件准备完成，未完成发送，所以不能进行下一轮文件发送
    messageModel!.isSendNextFile = false;

    for (int i = 0; i < totalChunks; i++) {
      final start = i * _chunkSize;
      final end = ((i + 1) * _chunkSize > bytes.length) ? bytes.length : (i + 1) * _chunkSize;
      final chunk = bytes.sublist(start, end);

      while (fileDataChannel.bufferedAmount! >= _chunkSize * 2) {
        // 等待直到buffer不那么满
        await Future.delayed(const Duration(milliseconds: 20));
      }
      // 发送切片
      await fileDataChannel.send(RTCDataChannelMessage.fromBinary(Uint8List.fromList(chunk)));
      // 设置发送文件的进度
      messageModel!.sendFileProgress += chunk.length;
      debugPrint("${messageModel!.sendFileProgress}");
    }

    await fileDataChannel.send(RTCDataChannelMessage(jsonEncode({"type":"end"})));
    debugPrint("文件发送完成");
    // 文件发送成功，可以进行下一轮文件发送
    messageModel!.isSendNextFile = true;
  }

  /*发送聊天消息*/
  sendChatMessage(String text) async{
    final key =  messageModel!.client.key;

    await sendOffer();
    final dataChannel = _dataChannelMap[key];

    final chatMessage = {
      "name":user?.name,
      "data":text
    };
    // 添加本地消息
    messageModel!.addChatMessage(TextChannelMessage.fromJsonLocal(chatMessage));
    await dataChannel!.chatDataChannel!.send(RTCDataChannelMessage(json.encode(chatMessage)));
  }


  close() async{
    for (final pc in _pcMap.values) {
      pc.dispose();
    }
    _pcMap.clear();
    if(_ws != null) {
      _ws!.sink.close();
      _ws = null;
    }

  }

}
